package asava.tools;

import asava.names.Name;
import asava.trees.*;
import asava.trees.Class;
import asava.utils.Flags;
import asava.utils.Operator;

import java.util.LinkedList;
import java.util.List;

import static asava.tools.Tokens.*;

/**
 * @author Tim Richter
 */
public final class Parser {

    private static boolean isExpr(final int token) {
        switch (token) {
            case INT:
            case NUMBER:
            case TRUE:
            case FALSE:
            case NULL:
            case IDENT:
            case NEW:
            case LPAREN:
            case LBRACK:
            case THIS:
            case SUPER:
                return true;
            default:
                return false;
        }
    }

    private final Reporter reporter;
    private final Names names;
    private final Scanner scanner;

    private int token;
    private int errorOffset;

    public Parser(final Reporter reporter,
                  final Names names) {
        this.reporter = reporter;
        this.names = names;
        this.scanner = new Scanner(reporter);
    }

    public Tree parse() {
        return unit();
    }

    private Tree unit() {
        accept(PACKAGE);
        final Name packageName = path();
        accept(LBRACE);
        final List<Tree> imports = importReferences();
        final Tree declaration = declaration();
        accept(RBRACE);
        return new Unit(packageName, imports, declaration);
    }

    private List<Tree> importReferences() {
        final List<Tree> imports = new LinkedList<>();

        while (token == IMPORT) {
            imports.add(importReference());
        }
        return imports;
    }

    private Tree importReference() {
        accept(IMPORT);
        int start = scanner.start();
        int end = scanner.end();
        boolean onDemand = false;

        while (move() == DOT) {
            if (move() == STAR) {
                onDemand = true;
                break;
            } else {
                end = scanner.end();
                accept(IDENT);
            }
        }
        sep();
        return new Import(new TypeRef(names.table.enter(scanner.tokenSource(start, end))), onDemand);
    }

    private Tree declaration() {
        final int modifiers = modifiers();
        switch (token) {
            case CLASS:
                return classDeclaration(modifiers);
            case INTERFACE:
                return classDeclaration(modifiers | Flags.INTERFACE);
            case FUNCTION:
                return functionDeclaration(modifiers);
            case VAR:
                return variableDeclaration(modifiers);
            default:
                error("expected top level declaration, but found " + Tokens.toString(token));
                return new Empty();
        }
    }

    private Tree classDeclaration(final int modifiers) {
        accept(CLASS);
        final Name name = ident();
        final Tree base = base();
        final List<Tree> interfaces = interfaces();
        accept(LBRACE);
        final List<Tree> fields = new LinkedList<>();
        final List<Tree> methods = new LinkedList<>();
        members(fields, methods);
        accept(RBRACE);
        return new Class(modifiers, name, base, interfaces, fields, methods);
    }

    private Tree base() {
        if (token == EXTENDS) {
            move();
            return typeRef();
        } else {
            return new Empty();
        }
    }

    private List<Tree> interfaces() {
        if (token == IMPLEMENTS) {
            move();
            return typeRefs();
        } else {
            return new LinkedList<>();
        }
    }

    private void members(final List<Tree> fields,
                         final List<Tree> functions) {
        while (true) {
            final int modifiers = modifiers();
            switch (token) {
                case FUNCTION:
                    functions.add(functionDeclaration(modifiers));
                    break;
                case CONST:
                case VAR:
                    fields.add(variableDeclaration(modifiers));
                    break;
                default:
                    if (modifiers != 0) {
                        error("expected class member, but found " + Tokens.toString(token));
                    }
                    return;
            }
        }
    }

    private Tree functionDeclaration(int modifiers) {
        accept(FUNCTION);
        if (token == GET) {
            move();
            modifiers |= Flags.GET;
        } else if (token == SET) {
            move();
            modifiers |= Flags.SET;
        }
        final Name name = ident();
        accept(LPAREN);
        final List<Tree> params = parameters();
        accept(COLON);
        final Tree result = typeRef();
        final Tree body = block();
        return new Function(modifiers, name, params, result, body);
    }

    private List<Tree> parameters() {
        final List<Tree> trees = new LinkedList<>();
        while (token == IDENT || token == DOTDOTDOT) {
            trees.add(parameter());
            if (token == COMMA) {
                move();
            } else {
                break;
            }
        }
        return trees;
    }

    private Tree parameter() {
        final Name name = ident();
        if (token == DOTDOTDOT) {
            move();
            return new Var(Flags.VARARGS, name, new Empty(), new Empty());
        } else {
            accept(COLON);
            final Tree type = typeRef();
            return new Var(0, name, type, new Empty());
        }
    }

    private Tree variableDeclaration(int modifiers) {
        if (token == VAR) {
            move();
        } else if (token == CONST) {
            move();
            modifiers |= Flags.CONST;
        } else {
            accept(VAR);
        }
        final Name name = ident();
        accept(COLON);
        final Tree type = typeRef();
        if (token == EQ) {
            move();
            final Tree init = expression();
            sep();
            return new Var(modifiers, name, type, init);
        } else {
            sep();
            return new Var(modifiers, name, type, new Empty());
        }
    }

    private Tree statement() {
        switch (token) {
            case BREAK:
                return breakStatement();
            case CONTINUE:
                return continueStatement();
            case DELETE:
                return deleteStatement();
            case DO:
                return doWhileStatement();
            case FOR:
                return forStatement();
            case IF:
                return ifStatement();
            case LBRACE:
                return block();
            case LPAREN:
            case RETURN:
                return returnStatement();
            case SWITCH:
                return switchStatement();
            case THROW:
                return throwStatement();
            case TRY:
                return tryStatement();
            case VAR:
            case CONST:
                return variableDeclaration(0);
            case WHILE:
                return whileStatement();
            default:
                return expressionStatement();
        }
    }

    private Tree breakStatement() {
        accept(BREAK);
        Name label;
        if (token == IDENT) {
            label = ident();
        } else {
            label = names.empty;
        }
        sep();
        return new Break(label, new Empty());
    }

    private Tree continueStatement() {
        accept(CONTINUE);
        Name label;
        if (token == IDENT) {
            label = ident();
        } else {
            label = names.empty;
        }
        sep();
        return new Continue(label, new Empty());
    }

    private Tree deleteStatement() {
        accept(DELETE);
        final Tree expr = expression();
        sep();
        return new Delete(expr);
    }

    private Tree forStatement() {
        accept(FOR);
        Tree init, expr, step, body;
        if (token == EACH) {
            accept(LPAREN);
            init = variableDeclaration(0);
            accept(IN);
            expr = expression();
            accept(RPAREN);
            if (token == LBRACE) {
                body = block();
            } else {
                body = new Empty();
            }
            return new ForEach(init, expr, body);
        } else {
            accept(LPAREN);
            if (token == VAR) {
                init = variableDeclaration(0);
                if (token == IN) {
                    accept(IN);
                    expr = expression();
                    accept(RPAREN);
                    body = block();
                    return new ForIn(init, expr, body);
                }
            } else {
                init = new Empty();
            }
            accept(SEMI);
            if (isExpr(token)) {
                expr = expression();
            } else {
                expr = new Empty();
            }
            accept(SEMI);
            if (isExpr(token)) {
                step = expression();
            } else {
                step = new Empty();
            }
            accept(RPAREN);
            if (token == LBRACE) {
                body = block();
            } else {
                accept(SEMI);
                body = new Empty();
            }
            return new For(init, expr, step, body);
        }
    }

    private Tree ifStatement() {
        accept(IF);
        accept(LPAREN);
        final Tree condition = expression();
        accept(RPAREN);
        final Tree thenPart = statement();
        if (token == ELSE) {
            accept(ELSE);
            return new If(condition, thenPart, statement());
        } else {
            return new If(condition, thenPart, new Empty());
        }
    }

    private Tree block() {
        final List<Tree> trees = new LinkedList<>();
        accept(LBRACE);
        while (token != LBRACE) {
            trees.add(statement());
        }
        accept(RBRACE);
        return new Block(trees);
    }

    private Tree returnStatement() {
        accept(RETURN);
        final Tree expr;
        if (isExpr(token)) {
            expr = expression();
        } else {
            expr = new Empty();
        }
        sep();
        return new Return(expr);
    }

    private Tree switchStatement() {
        accept(SWITCH);
        accept(LPAREN);
        final Tree selector = expression();
        accept(RPAREN);
        accept(LBRACE);
        final List<Tree> cases = caseStatements();
        accept(RBRACE);
        return new Switch(selector, cases);
    }

    private List<Tree> caseStatements() {
        final List<Tree> trees = new LinkedList<>();
        while (token == CASE) {
            trees.add(caseStatement());
        }
        return trees;
    }

    private Tree caseStatement() {
        accept(CASE);
        final Tree key;
        if (isExpr(token)) {
            key = expression();
        } else {
            key = new Empty();
        }
        accept(COLON);
        final Tree body = statement();
        return new Case(key, body);
    }

    private Tree throwStatement() {
        accept(THROW);
        final Tree expr;
        if (isExpr(token)) {
            expr = expression();
        } else {
            expr = new Empty();
        }
        sep();
        return new Throw(expr);
    }

    private Tree tryStatement() {
        accept(TRY);
        final Tree body = statement();
        final List<Tree> catches = catchStatements();
        final Tree finalizer;
        if (token == FINALLY) {
            accept(FINALLY);
            finalizer = statement();
        } else {
            finalizer = new Empty();
        }
        return new Try(body, catches, finalizer);
    }

    private List<Tree> catchStatements() {
        final List<Tree> trees = new LinkedList<>();
        while (token == CATCH) {
            trees.add(catchStatement());
        }
        return trees;
    }

    private Tree catchStatement() {
        accept(CATCH);
        accept(LPAREN);
        final Tree var = variableDeclaration(0);
        accept(RPAREN);
        final Tree body = statement();
        return new Catch(var, body);
    }

    private Tree whileStatement() {
        accept(WHILE);
        accept(LPAREN);
        final Tree condition = expression();
        accept(RPAREN);
        final Tree body;
        if (token == LBRACE) {
            body = block();
        } else {
            body = new Empty();
        }
        return new While(condition, body);
    }

    private Tree doWhileStatement() {
        accept(DO);
        final Tree body;
        if (token == LBRACE) {
            body = block();
        } else {
            body = new Empty();
        }
        accept(WHILE);
        accept(LPAREN);
        final Tree condition = expression();
        accept(RPAREN);
        return new DoWhile(condition, body);
    }

    private Tree expressionStatement() {
        final Tree expr = expression();
        sep();
        return expr;
    }

    private List<Tree> expressions() {
        final List<Tree> trees = new LinkedList<>();
        if (isExpr(token)) {
            trees.add(expression());
            while (token == COMMA) {
                trees.add(expression());
            }
        }
        return trees;
    }

    private Tree expression() {
        return infixExpression(prefixExpression());
    }

    private Tree prefixExpression() {
        final Operator operator = prefixOperator();
        if (operator == Operator.NONE) {
            return primary();
        } else {
            return new PrefixOp(primary(), operator);
        }
    }

    private Tree infixExpression(final Tree top) {

    }

    private Tree primary() {
        switch (token) {
            case THIS:
                accept(THIS);
                return select(new This());
            case SUPER:
                accept(SUPER);
                return select(new Super());
            case INT:
                accept(INT);
                return select(new Literal(Literal.INT, scanner.intLiteral()));
            case NUMBER:
                accept(NUMBER);
                return select(new Literal(Literal.NUMBER, scanner.numberLiteral()));
            case STRING:
                accept(STRING);
                return select(new Literal(Literal.STRING, scanner.stringLiteral()));
            case TRUE:
                accept(TRUE);
                return new Literal(Literal.BOOLEAN, Boolean.TRUE);
            case FALSE:
                accept(FALSE);
                return new Literal(Literal.BOOLEAN, Boolean.FALSE);
            case NULL:
                accept(NULL);
                return new Literal(Literal.NULL, null);
            case NEW: {
                accept(NEW);
                final Tree type = typeRef();
                return apply(new NewClass(type));
            }
            case IDENT:
                return select(new Ident(ident()));
            case LPAREN: {
                accept(LPAREN);
                final Tree expr = expression();
                accept(RPAREN);
                return new Parens(expr);
            }
            case LBRACK: {
                accept(LBRACK);
                final List<Tree> args = expressions();
                accept(RBRACK);
                return new NewArray(args);
            }
            default:
                error("expected expression, but found " + Tokens.toString(token));
                return new Empty();
        }
    }

    private Tree select(final Tree selector) {
        if (token == DOT) {
            accept(DOT);
            return select(new Select(selector, ident()));
        } else if (token == LPAREN) {
            return apply(selector);
        } else {
            return selector;
        }
    }

    private Tree apply(final Tree selector) {
        accept(LPAREN);
        final List<Tree> args = expressions();
        accept(RPAREN);
        return new Apply(selector, args);
    }

    private List<Tree> typeRefs() {
        final List<Tree> trees = new LinkedList<>();
        trees.add(typeRef());

        while (token == COMMA) {
            move();
            trees.add(typeRef());
        }
        return trees;
    }

    private Tree typeRef() {
        if (token == STAR) {
            return new Empty();
        } else {
            return new TypeRef(path());
        }
    }

    private Operator prefixOperator() {
        switch (token) {
            case PLUS:
                return Operator.PLUS;
            case PLUSPLUS:
                return Operator.INCR;
            case MIN:
                return Operator.MIN;
            case MINMIN:
                return Operator.DECR;
            case NOT:
                return Operator.NOT;
            case TILDE:
                return Operator.BITNOT;
            default:
                return Operator.NONE;
        }
    }

    private Operator infixOperator() {
        switch (token) {
            case PLUS:
                return Operator.ADD;
            case PLUSEQ:
                return Operator.ADDASSIGN;
            case MIN:
                return Operator.SUB;
            case MINEQ:
                return Operator.SUBASSIGN;
            case STAR:
                return Operator.MUL;
            case STAREQ:
                return Operator.MULASSIGN;
            case SLASH:
                return Operator.DIV;
            case SLASHEQ:
                return Operator.DIVASSIGN;
            case PERC:
                return Operator.MOD;
            case PERCEQ:
                return Operator.MODASSIGN;
            case EQ:
                return Operator.ASSIGN;
            case EQEQ:
                return Operator.EQ;
            case EQEQEQ:
                return Operator.STREQ;
            case NOTEQ:
                return Operator.NE;
            case NOTEQEQ:
                return Operator.STRNE;
            case GT:
                return Operator.GT;
            case GTEQ:
                return Operator.GE;
            case GTGT:
                return Operator.RSH;
            case GTGTEQ:
                return Operator.RSHASSIGN;
            case GTGTGT:
                return Operator.URSH;
            case GTGTGTEQ:
                return Operator.URSHASSIGN;
            case LT:
                return Operator.LT;
            case LTEQ:
                return Operator.LE;
            case LTLT:
                return Operator.LSH;
            case LTLTEQ:
                return Operator.LSHASSIGN;
            case AMP:
                return Operator.BITAND;
            case AMPAMP:
                return Operator.AND;
            case AMPEQ:
                return Operator.BITANDASSIGN;
            case BAR:
                return Operator.BITOR;
            case BARBAR:
                return Operator.OR;
            case BAREQ:
                return Operator.BITORASSIGN;
            case CARET:
                return Operator.BITXOR;
            case CARETEQ:
                return Operator.BITXORASSIGN;
            default:
                return Operator.NONE;
        }
    }

    private Operator postfixOperator() {
        switch (token) {
            case PLUSPLUS:
                return Operator.INCR;
            case MINMIN:
                return Operator.DECR;
            default:
                return Operator.NONE;
        }
    }

    private int modifiers() {
        int modifiers = 0;

        while (true) {
            switch (token) {
                case PUBLIC:
                    if ((modifiers & Flags.PUBLIC) != 0) {
                        error("duplicate modifier");
                    } else if ((modifiers & (Flags.PRIVATE | Flags.PROTECTED | Flags.INTERNAL)) != 0) {
                        error("illegal modifier");
                    } else {
                        modifiers |= Flags.PUBLIC;
                    }
                    break;
                case PRIVATE:
                    if ((modifiers & Flags.PRIVATE) != 0) {
                        error("duplicate modifier");
                    } else if ((modifiers & (Flags.PUBLIC | Flags.PROTECTED | Flags.INTERNAL)) != 0) {
                        error("illegal modifier");
                    } else {
                        modifiers |= Flags.PRIVATE;
                    }
                    break;
                case PROTECTED:
                    if ((modifiers & Flags.PROTECTED) != 0) {
                        error("duplicate modifier");
                    } else if ((modifiers & (Flags.PUBLIC | Flags.PRIVATE | Flags.INTERNAL)) != 0) {
                        error("illegal modifier");
                    } else {
                        modifiers |= Flags.PROTECTED;
                    }
                    break;
                case INTERNAL:
                    if ((modifiers & Flags.INTERNAL) != 0) {
                        error("duplicate modifier");
                    } else if ((modifiers & (Flags.PUBLIC | Flags.PRIVATE | Flags.PROTECTED)) != 0) {
                        error("illegal modifier");
                    } else {
                        modifiers |= Flags.INTERNAL;
                    }
                    break;
                case DYNAMIC:
                    if ((modifiers & Flags.DYNAMIC) != 0) {
                        error("duplicate modifier");
                    } else {
                        modifiers |= Flags.DYNAMIC;
                    }
                    break;
                case FINAL:
                    if ((modifiers & Flags.FINAL) != 0) {
                        error("duplicate modifier");
                    } else {
                        modifiers |= Flags.FINAL;
                    }
                    break;
                case NATIVE:
                    if ((modifiers & Flags.NATIVE) != 0) {
                        error("duplicate modifier");
                    } else {
                        modifiers |= Flags.NATIVE;
                    }
                    break;
                case STATIC:
                    if ((modifiers & Flags.STATIC) != 0) {
                        error("duplicate modifier");
                    } else {
                        modifiers |= Flags.STATIC;
                    }
                    break;
                case OVERRIDE:
                    if ((modifiers & Flags.OVERRIDE) != 0) {
                        error("duplicate modifier");
                    } else {
                        modifiers |= Flags.OVERRIDE;
                    }
                    break;
                default:
                    return modifiers;
            }
        }
    }

    private Name ident() {
        if (token == IDENT) {
            final Name ident = names.table.enter(scanner.tokenSource());
            move();
            return ident;
        } else {
            accept(IDENT);
            return names.empty;
        }
    }

    private Name path() {
        if (token == IDENT) {
            final int start = scanner.start();

            while (move() == DOT) {
                move();
                accept(IDENT);
            }
            return names.table.enter(scanner.tokenSource(start, scanner.end()));
        }
        return names.empty;
    }

    private void sep() {
        if (token == SEMI) {
            move();
        } else if (!scanner.nl()) {
            accept(SEMI);
        }
    }

    private void accept(final int expectedToken) {
        if (token != expectedToken) {
            expected(expectedToken);
            skip(expectedToken);
        } else {
            move();
        }
    }

    private void expected(final int expectedToken) {
        error("expected " + Tokens.toString(expectedToken) + ", but found " + Tokens.toString(token));
    }

    private void error(final String message) {
        if (errorOffset < scanner.end()) {
            errorOffset = scanner.end();
            reporter.error(scanner.position(), message);
        }
    }

    private void skip(final int sentinelToken) {
        int nParens = 0;
        int nBraces = 0;

        while (true) {
            switch (token) {
                case SEMI:
                    if (nParens == 0 && nBraces == 0) {
                        return;
                    }
                    break;
                case LPAREN:
                    nParens++;
                    break;
                case RPAREN:
                    nParens--;
                    break;
                case LBRACE:
                    nBraces++;
                    break;
                case RBRACE:
                    nBraces--;
                    break;
                case EOF:
                    return;
                default:
                    if (scanner.nl() && nParens == 0 && nBraces == 0) {
                        return;
                    }
                    break;
            }
            if (token == sentinelToken && nParens == 0 && nBraces == 0) {
                move();
                return;
            }
            move();
        }
    }

    private int move() {
        token = scanner.nextToken();
        return token;
    }
}
